﻿using System;
using System.Collections.Generic;
using System.IO;
using EmbeddedSharp.Core;
using EmbeddedSharp.Scripting;
using Zove.Runtime.Core;
using Zove.Runtime.Core.Zoom;

namespace Zove.Scripting.Core
{
    public class ZoveScriptingContext : ScriptingContext, IZoveScriptingContext
    {
        protected readonly DefaultFunctionManager FunctionManager;

        protected readonly ZoveScriptExpressionParserFactory ParserFactory;

        protected Dictionary<string, ZoveObject> Instances
            = new Dictionary<string, ZoveObject>();

        public ZoveScriptingContext()
        {
            FunctionManager = new ZoomFunctionManager(this, new ZoomLanguageNature()); // ContextFunctionManager(this);
            ParserFactory = new ZoveScriptExpressionParserFactory(this);
        }

        #region IZoveScriptingContext Members

        /// <summary>
        /// Run the script
        /// </summary>
        /// <param name="fileName"></param>
        public override void Run(string fileName)
        {
            string source = File.ReadAllText(fileName);
            var script
                = new ZoveScriptingObject(source, this);
            script.Run();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public override object Evaluate(string expression)
        {
            ExpressionParser pa
                = GetParserFactory().CreateParser(
                    expression, this);
            Node root = pa.Parse();
            if (root is ContextFuncNode)
            {
                (root as ContextFuncNode).Context = this;
            }
            object result = root.Evaluate();

            return result;
        }

        /// <summary>
        /// Get variable
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public override object GetVariable(string value)
        {
            int dotIndex = value.IndexOf('.');

            if (dotIndex < 0)
            {
                return base.GetVariable(value);

                //if (obj is FactorNode) {
                //    return obj as FactorNode;
                //} else if (obj is NumberNode) {
                //    return obj as NumberNode;
                //} else {
                //    return obj;
                //}
            }

            RuntimeFactor factor = GetFactor(value);
            if (factor != null)
            {
                return factor.Value;
            }

            return null;
            //if (instances.ContainsKey(instanceName))
            //{
            //    Solution solution = instances[instanceName];
            //    RuntimeModel rm = solution.Model;
            //    RuntimeFactor factor = rm.GetFactorByName(factorName);
            //    FactorNode fn = new FactorNode(factor);

            //    return fn;
            //}
            //else
            //{
            //    throw new Exception(
            //        string.Format(
            //        "Solution '{0}' is not instantiated.", instanceName));
            //}
        }

        /// <summary>
        /// Assign to a factor
        /// </summary>
        /// <param name="variableName"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override object Assign(
            string variableName, object result)
        {
            if (variableName.IndexOf('.') > 0)
            {
                RuntimeFactor factor = GetFactor(variableName);
                if (factor == null)
                {
                    throw new FactorNotDeclaredException(
                        variableName);
                }

                double value = Convert.ToDouble(result);
                factor.Initialize(value);

                //string instanceName = variableName.Split('.')[0];
                //this.EvaluateSolution(instanceName);

                return result;
            }

            return base.Assign(variableName, result);
            //if (Utils.IsNumeric(result)) {
            //    FactorNode fn 
            //        = this.GetVariable(variableName) as FactorNode;
            //    if (fn == null) {
            //        if (!IsLegalNaming(variableName)) {
            //            throw new Exception(
            //                "The naming is not complied with the convention: " + variableName);
            //        }

            //        RuntimeFactor factor = new RuntimeFactor();
            //        factor.Name = variableName;
            //        factor.Value = Convert.ToDouble(result);

            //        fn = new FactorNode(factor);
            //        base.AddVariable(variableName, fn);
            //    }

            //    if (fn.Factor.Status == RuntimeFactorStatus.Unknown) {
            //        fn.Factor.Status = RuntimeFactorStatus.Input;
            //    } // if not, remain the current status

            //    fn.Factor.Value = Convert.ToDouble(result);

            //    this.EvaluateSolution();
            //    return fn;
            //}

            //return base.Assign(variableName, result);
        }

        /// <summary>
        /// Get a factor
        /// </summary>
        /// <param name="variableName"></param>
        /// <returns></returns>
        public RuntimeFactor GetFactor(string variableName)
        {
            if (variableName.IndexOf('.') < 0)
            {
                return null;
            }

            int index = variableName.IndexOf('.');
            string instanceName = variableName.Substring(0, index);
            string var = variableName.Substring(index + 1, variableName.Length - index - 1);
            ZoveObject instance = GetInstance(instanceName);

            //return instance.Model.GetFactorByName(variableName);

            return instance.Model.Factors[var];
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public ZoveObject GetInstance(string id)
        {
            StackFrame frame = Stack.Peek();
            if (frame.ContainsKey(id))
            {
                return frame[id] as ZoveObject;
            }
            if (frame != RootFrame)
            {
                if (RootFrame.ContainsKey(id))
                {
                    return RootFrame[id] as ZoveObject;
                }
            }

            return null;
        }

        /// <summary>
        /// Remove the variable
        /// </summary>
        /// <param name="p">the variable to be removed</param>
        public override void RemoveVariable(string p)
        {
            if (p.IndexOf('.') < 0)
            {
                base.RemoveVariable(p);
            }

            Instances.Remove(p);
        }

        /// <summary>
        /// Check if the specific variable exists
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public override bool HasVariable(string p)
        {
            if (base.HasVariable(p))
            {
                return true;
            }

            return Instances.ContainsKey(p);
        }

        public override IExpressionParserFactory GetParserFactory()
        {
            return ParserFactory;
        }

        public override IFunctionManager GetFunctionManager()
        {
            return FunctionManager;
        }

        public override IUnaryFunctionFactory UnaryFunctionFactory
        {
            get { return new ZoveScriptingUnaryFunctionFactory(this); }
        }

        #endregion

        /// <summary>
        /// Add a new varible to the context
        /// </summary>
        /// <param name="factorName">the variable name</param>
        /// <returns></returns>
        //public override Node AddVariable(string factorName) {
        //    if (!IsLegalNaming(factorName)) {
        //        throw new IllegalNamingException(factorName);
        //    }
        //    RuntimeFactor rf = new RuntimeFactor(factorName);
        //    rf.Status = RuntimeFactorStatus.Unknown;
        //    FactorNode fn = new FactorNode(rf);
        //    return fn;
        //}
        /// <summary>
        /// Create a node expression
        /// </summary>
        /// <param name="expr"></param>
        /// <returns></returns>
        public override Node Parse(string expr)
        {
            ExpressionParser p = ParserFactory.CreateParser(expr, this);
            Node root = p.Parse();
            return root;
        }

        /// <summary>
        /// Add a new solution to the context
        /// </summary>
        /// <param name="name">name of the instance</param>
        /// <param name="instance">the solution instance</param>
        public void AddInstance(string name, ZoveObject instance)
        {
            if (!IsLegalNaming(name))
            {
                throw new IllegalNamingException(name);
            }

            //SolutionNode sn = new SolutionNode(instance);
            StackFrame frame = base.Stack.Peek();
            if (frame.ContainsKey(name))
            {
                frame[name] = instance;
            }
            else
            {
                frame.Add(name, instance);
            }
        }

        public static bool IsLegalNaming(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                return false;
            }

            if (char.IsDigit(name[0]))
            {
                return false;
            }

            foreach (char c in name)
            {
                if (!(char.IsLetterOrDigit(c) || c == '_'))
                {
                    return false;
                }
            }

            return true;
        }

        public object[] GetAllVariables()
        {
            throw new NotImplementedException();
        }
    }
}